#coding: utf-8
import uiautomator2 as u2
import time
import logging
import sys
import traceback
import os
import functools

IP_ADRESS = '192.168.100.22'
PEANUT_PACKAGE_NAME = 'com.keenon.peanut.solutionhotel'

class PeanutAPP:
    def __init__(self, device):
        # logger
        LOG_FORMAT = "%(asctime)s[APP]%(message)s"
        DATE_FORMAT = "[%Y/%m/%d %H:%M:%S]"

        self.logger = logging.getLogger('app')
        self.logger.setLevel(logging.INFO)
        formatter = logging.Formatter(fmt=LOG_FORMAT, datefmt=DATE_FORMAT)
        handler = logging.StreamHandler(sys.stdout)
        handler.setFormatter(formatter)
        self.logger.addHandler(handler)

        global PEANUT_PACKAGE_NAME
        self.device = device
        # 设置元素查找等待时间，默认为 20
        self.device.implicitly_wait(20)
        # 设置模拟操作延时，操作前无延时，操作后无延时（单位秒）
        self.device.settings['operation_delay'] = (0, 0)
        # 设置最大空闲时间为 86400 秒（一天），超时无命令则断开 uiautomator 的连接
        self.device.set_new_command_timeout(86400)
        # 获取包名
        self.package_name = PEANUT_PACKAGE_NAME
        # 初始化 activities
        self.activities = {}
        self.__init_activities()
        # 获取 main activity
        self.main_activity = 'com.keenon.peanut.peanutsetting.activity.SetupActivity'
        # 舱门是否打开
        self.is_hatch_open = False
        # 启动 app 到初始菜单界面
        # self.stop_charge()
        # self.launch_delivery_main_menu_page()

    def __init_activities(self):
        """初始化所有 activities, 记录到 self.activities 中
        """
        self.activities['菜单'] = 'com.keenon.peanut.peanutmain.activity.MenuActivity'
        self.activities['送物菜单'] = 'com.keenon.peanut.solutionhotel.activity.DeliveryMainActivity'
        self.activities['输入密码'] = 'com.keenon.peanut.solutionhotel.activity.PassWordActivity'
        self.activities['输入目标'] = 'com.keenon.peanut.solutionhotel.activity.InputDestinationActivity'
        self.activities['设置'] = 'com.keenon.peanut.solutionhotel.activity.UserSettingActivity'
        self.activities['充电'] = 'com.keenon.peanut.peanutsetting.activity.AutoChargeActivity'
        self.activities['定位'] = 'com.keenon.peanut.peanutmain.activity.LocationActivity'
        self.activities['送物等待'] = 'com.keenon.peanut.solutionhotel.activity.ArrivedActivity'
        self.activities['导航'] = 'com.keenon.peanut.solutionhotel.activity.NavigationActivity'

    def retry(times=3, fail_text="", suc_text=""):
        """decoretor to retry functions
        """
        def decorator(func):
            def wrapper(self, *args, retry_times=times, **kw):
                res = None
                while retry_times > 0:
                    try:
                        res = func(self, *args, **kw)
                    except:
                        traceback.print_exc()
                        res = None
                    finally:
                        retry_times -= 1
                        if res:
                            self.logger.info(suc_text)
                            break
                        else:
                            res = None
                            self.logger.error(fail_text + " 重试中...")
                if not res:
                    self.logger.error(fail_text)
                return res
            return wrapper
        return decorator

    def __in_page(self, activity):
        """检查当前页面是否为指定的 activity

        Args:
            activity_name (str): 要检查的 activity

        Returns:
            bool: 是或否
        """
        current_activity = self.device.app_current()['activity']
        if current_activity == activity:
            return True
        else:
            return False

    @retry(times=3, fail_text="无法打开初始界面", suc_text="打开初始界面")
    def launch_main_activity(self):
        """启动 app 或将 app 返回到 main activity，即初始菜单界面

        Returns:
            bool: 是否启动 app 到初始界面
        """
        self.exit_delivery()
        if not self.__in_page(self.main_activity):
            self.stop_charge()
            self.device.app_start(self.package_name, self.main_activity)
            return self.device.wait_activity(self.main_activity, timeout=3)
        else:
            return True

    @retry(times=3, fail_text="无法打开主菜单", suc_text="打开主菜单")
    def launch_delivery_main_menu_page(self):
        """打开送物菜单界面

        Returns:
            bool: 是否打开送物菜单
        """
        self.exit_delivery()
        if not self.__in_page(self.activities['送物菜单']):
            self.stop_charge()
            self.launch_main_activity()
            self.device(text="返回主页").click()
            return self.device.wait_activity(self.activities['送物菜单'], timeout=3)
        else:
            return True

    @retry(times=3, fail_text="无法输入密码", suc_text="密码已输入")
    def enter_password(self, password="0000", next_activity_name='设置'):
        """输入密码

        Returns:
            bool: 是否成功输入密码并进入下一个页面
        """
        if self.device.wait_activity(self.activities['输入密码'], timeout=3):
            for c in password:
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/num_%s" % c).click(timeout=2)
            # 判断是否打开目标页面
            if self.device.wait_activity(self.activities[next_activity_name], timeout=3):
                return True
            else:
                self.device('com.keenon.peanut.solutionhotel:id/num_reset').click()
                return False
        else:
            return False

    @retry(times=3, fail_text="无法打开设置界面", suc_text="打开设置界面")
    def launch_setting_page(self):
        """打开设置界面

        Returns:
            bool: 是否打开设置界面
        """
        self.stop_charge()
        self.exit_delivery()
        if not self.__in_page(self.activities['设置']):
            self.launch_delivery_main_menu_page()   # 启动到送物界面
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_setting").click_exists(timeout=5) # 点击设置按钮
            return self.enter_password()
        else:
            return True

    @retry(times=3, fail_text="无法打开通用配送界面", suc_text="打开通用配送界面")
    def launch_delivery_page(self):
        """打开通用配送界面

        Returns:
            bool: 是否打开通用配送界面
        """
        self.stop_charge()
        if not self.__in_page(self.activities['输入目标']):
            self.launch_delivery_main_menu_page()
            # 配送和派遣的 resourceId 都是 business_dec, 不知道为什么配送不能用 text 属性来筛选, 只能取 index
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/business_dec")[0].click_exists(timeout=5)
            return self.device.wait_activity(self.activities['输入目标'], timeout=10.0)
        else:
            return True

    def exit_delivery(self):
        if self.__in_page(self.activities['输入目标']):
            self.device.press('back')


    @retry(times=3, fail_text='无法执行送物', suc_text='送物开始')
    def delivery(self, target_name = None, phone_number = None, wait=True):
        """执行「通用配送」

        Args:
            target_name (str, optional): 送物目标 ID. Defaults to None.
            phone_number (str, optional): 送物目标手机号. Defaults to None.
            wait (boo, optional): 输入完目标(和手机号)后是否原地等待, 如果否则立刻执行送物. Defaults to True.
        """
        self.stop_charge()
        if not self.__in_page(self.activities['输入目标']):
            if not self.launch_delivery_page():
                return False
            
        if target_name:
            try:
                # 点击送物目的地
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/name", text=target_name).click()
                if self.device(resourceId='com.keenon.peanut.solutionhotel:id/ed_telephone').exists:
                    # 输入手机号
                    self.device(resourceId='com.keenon.peanut.solutionhotel:id/ed_telephone', text='请输入手机号').click()
                    if not phone_number:
                        phone_number == '17700000000'
                    self.device(focused=True).set_text(phone_number)
                if not wait:
                    # 如果不等待则关门出发
                    self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_go", text='关门出发').click()
            except:
                raise RuntimeError
            else:
                if phone_number:
                    self.logger.info("目的地: %s, 手机号码: %s" % (target_name, phone_number))
                else:
                    self.logger.info("目的地: %s" % target_name)
                return True
        else:
            # 如果要求等待, 则流程结束, 否则 target_name 不能为空.
            return wait

    def delivery_wait(self, target_name = None, phone_number = None):
        """仅作为调用 delivery 的接口使用，且 wait 参数为 True. 所有代码已经迁移到 delivery() 方法.

        Args:
            target_name (str, optional): 送物目标 ID. Defaults to None.
            phone_number (str, optional): 送物目标手机号. Defaults to None.

        Returns:
            Bool: 是否成功输入手机号和地点
        """
        temp = self.delivery(target_name, phone_number, wait=True)
        self.set_hatch_status(True)
        return temp

    def delivery_input(self, target_name, phone_number=None):
        """仅作为调用 delivery 的接口使用，且 wait 参数为 True. 所有代码已经迁移到 delivery() 方法.

        Args:
            target_name (str, optional): 送物目标 ID. Defaults to None.
            phone_number (str, optional): 送物目标手机号. Defaults to None.

        Returns:
            Bool: 是否成功输入手机号和地点
        """
        return self.delivery(target_name, phone_number, wait=True)

    def delivery_close_hatch(self):
        """在「通用配送」界面关闭舱门
        """
        self.device(resourceId="com.keenon.peanut.solutionhotel:id/gating_control_dec").click()
        self.device(textContains="请稍等").wait_gone(timeout=8.0)

    @retry(times=3, fail_text="无法执行关门出发", suc_text="关门出发")
    def delivery_go(self):
        """仅用于「通用配送」界面的「关门出发」按钮的点击. 需要和 delivery 方法 (wait=True) 一起使用.
        """
        self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_go", text='关门出发').click_exists()
        return self.__in_page(self.activities['导航'])

    def open_hatch_on_delivered(self, phone_number):
        """送物到达目的地时打开舱门.
        """
        self.logger.info('正在打开舱门...')
        try:
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_open").wait(timeout=10)
            clicked = self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_open").click_gone(maxretry=15, interval=1.0)
            if clicked:
                self.logger.info("点击打开舱门按钮")
            self.device(textContains="请稍等").wait_gone(timeout=10)
            return True
        except:
            traceback.print_exc()

    def dispatch(self, target_name = None):
        """执行「派遣」

        Args:
            target_name (str, optional): 派遣目的地 ID. Defaults to None.
        """
        self.launch_delivery_main_menu_page()
        self.device(resourceId="com.keenon.peanut.solutionhotel:id/business_dec", text="派遣").click()
        self.device.wait_activity(self.activities['输入目标'])
        if target_name:
            # 点击送物目的地
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/name", text=target_name).click()
            # 派遣出发
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_go", text='派遣出发').click()
            self.logger.info('派遣到 %s' % target_name)

    def charge(self):
        """在 App 设置中执行充电
        """
        if self.__in_page(self.activities['充电']):
            self.logger.info('充电中...')
            return
        if not self.__in_page(self.activities['设置']):
            self.launch_setting_page()
        self.device(resourceId="com.keenon.peanut.solutionhotel:id/item_wifi").click()  # 真的叫 item_wifi, 不是我写错, 是 APP 的 resource-id 写错了
        self.device(resourceId="com.keenon.peanut.solutionhotel:id/trigger_charge_view").click_exists(timeout=5)
        self.logger.info("返回充电")

    def stop_charge(self):
        """在 App 中停止充电. 建议在所有页面跳转之前调用一次. 因为在充电状态下机器人会一直执行回到充电桩的指令.
        """
        if self.__in_page(self.activities['充电']):
            try:
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/stop_charge_tv", text='停止充电').click()
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/selection_start_work_tv", text='开始送物').wait(timeout=3)
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/selection_start_work_tv", text='开始送物').click()
                self.device(resourceId="com.keenon.peanut.solutionhotel:id/selection_start_work_tv", text='开始送物').wait_gone(timeout=3)
                self.logger.info('停止充电')
            except:
                traceback.print_exc()
                self.logger.warning("停止充电可能失败，请确认花生状态。")
            finally:
                if self.device.wait_activity(self.activities['送物菜单']):
                    return True
                else:
                    return False
        else:
            return True
            
    def open_hatch(self):
        """在 App 设置中打开舱门
        """
        if not self.is_hatch_open:
            self.launch_setting_page()
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_w1_control_gating").click()
            self.logger.info('正在打开舱门...')
            self.set_hatch_status(True)
    
    def close_hatch(self):
        """在 App 设置中关闭舱门
        """
        if self.is_hatch_open:
            self.launch_setting_page()
            self.device(resourceId="com.keenon.peanut.solutionhotel:id/tv_w1_control_gating").click()
            self.logger.info('正在关闭舱门...')
            self.set_hatch_status(False)
    
    def set_hatch_status(self, is_hatch_open):
        """设置舱门开关的标志

        Args:
            is_hatch_open (bool): 舱门是否打开
        """
        with self.device.watch_context() as ctx:
            ctx.wait_stable()
            self.is_hatch_open = is_hatch_open
            self.logger.info('舱门已{}'.format('打开' if is_hatch_open else '关闭'))
            
    def battery_info(self):
        return int(self.device(resourceId='com.keenon.peanut.solutionhotel:id/tv_progress').get_text()[:-1])

    def click_screen(self, pos=(0, 0), retry = 10):
        for _ in range(retry):    
            try:
                self.device.click(pos[0], pos[1])
                break
            except:
                traceback.print_exc()
                self.logger.error('Click screen error, try reconnect to uiautomator2.')
                os.system('adb kill-server')
                self.device = u2.connect_adb_wifi(IP_ADRESS+':5555')

    def swipe(self, direction='right'):
        self.device.swipe_ext(direction)
                

if __name__ == "__main__":
    device = u2.connect_adb_wifi(IP_ADRESS+':5555')
    app = PeanutAPP(device)
